/*
 * (C)opyright 2010-2011, pengliu.2002@gmail.com
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkrgb.h>

#include <format_defs.h>
#include <openjpeg.h>

#include "TJpeg2000Plugin.h"

// general initialization and shutdown                                                              
NPError NS_PluginInitialize()
{
  return NPERR_NO_ERROR;
}

void NS_PluginShutdown()
{
  // Empty
}

char* NS_GetMIMEDescription()
{
  return (char*)"image/jp2:j2c:JPEG2000 image;image/j2c:j2c:JPEG2000 image;";
}

// construction and destruction of our plugin instance object
nsPluginInstanceBase*
NS_NewPluginInstance(nsPluginCreateData* aCreateDataStruct)
{
  if(!aCreateDataStruct)
    return NULL;

  TJpeg2000Plugin* plugin
    = new TJpeg2000Plugin(aCreateDataStruct->instance);

  // now is the time to tell Mozilla that we are windowless
  NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, NULL);

  return plugin;
}

void NS_DestroyPluginInstance(nsPluginInstanceBase* aPlugin)
{
  if(aPlugin)
    delete (TJpeg2000Plugin*)aPlugin;
}

TJpeg2000Plugin::TJpeg2000Plugin(NPP instance)
  : Jpeg2000Buffer(NULL),
    Jpeg2000BufferSize(0),
    Jpeg2000DataLen(0),
    RawBuffer(NULL)
{
  // Empty
}

TJpeg2000Plugin::~TJpeg2000Plugin()
{
  free(Jpeg2000Buffer);
  free(RawBuffer);
  Jpeg2000Buffer = NULL;
  RawBuffer = NULL;
}

NPBool
TJpeg2000Plugin::init(NPWindow* window)
{
  return true;
}

void
TJpeg2000Plugin::shut()
{
  free(RawBuffer);
  RawBuffer = NULL;
}

NPBool
TJpeg2000Plugin::isInitialized()
{
  return true;
}

NPError
TJpeg2000Plugin::SetWindow(NPWindow *window)
{
  Window = *window;
  return NPERR_NO_ERROR;
}

NPError
TJpeg2000Plugin::NewStream(NPMIMEType type, NPStream *stream,
                           NPBool seekable, uint16_t *stype)
{
  *stype = NP_NORMAL;

  Jpeg2000BufferSize = JPEG2000_BUFFER_SIZE_INIT;
  Jpeg2000DataLen = 0;
  Jpeg2000Buffer = (unsigned char *) malloc(Jpeg2000BufferSize);
  if (Jpeg2000Buffer == NULL) {
    fprintf(stderr, "[TJpeg2000Plugin::NewStream] Out of Memory!\n"); 
    return NPERR_OUT_OF_MEMORY_ERROR;
  }
  
  return NPERR_NO_ERROR;
}

NPError
TJpeg2000Plugin::DestroyStream(NPStream *stream, NPError reason)
{
  if (reason == NPRES_DONE) {
    Jpeg2000Decode();
  }

  return NPERR_NO_ERROR;
}

int32_t
TJpeg2000Plugin::WriteReady(NPStream *stream)
{
  return CHUNK_SIZE;
}

int32_t
TJpeg2000Plugin::Write(NPStream *stream, int32_t offset,
                       int32_t len, void *buffer)
{
  unsigned char *tmp;

  if (offset + len > Jpeg2000BufferSize) {
    if (Jpeg2000BufferSize < JPEG2000_BUFFER_SIZE_MAX) {
      Jpeg2000BufferSize <<= 1;
    }
    tmp = (unsigned char *) realloc(Jpeg2000Buffer, Jpeg2000BufferSize);
    if (tmp == NULL) {
      free(Jpeg2000Buffer);
      Jpeg2000Buffer = NULL;
      fprintf(stderr, "[TJpeg2000Plugin::Write] Failed to realloc!\n");
      return -1;
    }
    else {
      Jpeg2000Buffer = tmp;
    }
  }

  Jpeg2000DataLen += len;
  memcpy(Jpeg2000Buffer + offset,
         buffer,
         len);

  return len;
}

uint16_t
TJpeg2000Plugin::HandleEvent(void *event)
{
  XEvent *nativeEvent = (XEvent*) event;
  
  if (nativeEvent->type != GraphicsExpose)
    return 0;

  XGraphicsExposeEvent *expose = &nativeEvent->xgraphicsexpose;
  Window.window = (void*) (expose->drawable);
  
  GdkNativeWindow nativeWinId = (XID) (Window.window);
  GdkDrawable *gdkWindow = GDK_DRAWABLE(gdk_window_foreign_new(nativeWinId));
  DrawWindow(gdkWindow);
  g_object_unref(gdkWindow);

  return 1;
}

NPError
TJpeg2000Plugin::GetValue(NPPVariable variable, void *value)
{
  switch(variable) {
  case NPPVpluginNameString:
    *((char **)value) = (char*)"JPEG2000 Plugin";
    break;
  case NPPVpluginDescriptionString:
    *((char **)value) = (char*)"The <a href=\"http://code.google.com/p/jpeg2000-plugin\">JEPG2000 Plugin</a> version 0.1 handles JPEG2000 images";
    break;
  default:
    return NPERR_GENERIC_ERROR;
  }
  return NPERR_NO_ERROR;
}

void
TJpeg2000Plugin::OpenJpegErrorCallback(const char *msg, void *client_data)
{
  FILE *stream = (FILE*)client_data;
  fprintf(stream, "[ERROR] %s", msg);
}

void
TJpeg2000Plugin::OpenJpegWarningCallback(const char *msg, void *client_data)
{
  FILE *stream = (FILE*)client_data;
  fprintf(stream, "[WARNING] %s", msg);
}

void
TJpeg2000Plugin::OpenJpegInfoCallback(const char *msg, void *client_data)
{
  (void)client_data;
  fprintf(stdout, "[INFO] %s", msg);
}

void
TJpeg2000Plugin::DrawWindow(GdkDrawable *gdkWindow)
{
  int x = Window.x;
  int y = Window.y;
  int width = Window.width;
  int height = Window.height;

  if (RawBuffer) {
    GdkGC* gdkContext = gdk_gc_new(gdkWindow);

    gdk_rgb_init();
    gdk_draw_rgb_image(gdkWindow, gdkContext,
                       x, y, width, height,
                       GDK_RGB_DITHER_NONE,
                       RawBuffer, width*3);
    g_object_unref(gdkContext);
  }
}

NPBool
TJpeg2000Plugin::Jpeg2000Decode()
{
  unsigned char *data;
  int32_t length;
  opj_dinfo_t* dinfo = NULL;
  opj_cio_t *cio = NULL;
  opj_dparameters_t parameters;
  opj_event_mgr_t event_mgr;
  opj_image_t *image;
  int width, height;
 
  data = Jpeg2000Buffer;
  length = Jpeg2000DataLen;
 
  /* configure the event callbacks (not required) */
  memset(&event_mgr, 0, sizeof(opj_event_mgr_t));
  event_mgr.error_handler = OpenJpegErrorCallback;
  event_mgr.warning_handler = OpenJpegWarningCallback;
  event_mgr.info_handler = OpenJpegInfoCallback;
  
  opj_set_default_decoder_parameters(&parameters);
  parameters.decod_format = J2K_CFMT;
  parameters.cod_format = BMP_DFMT;
  
  dinfo = opj_create_decompress(CODEC_J2K);
  opj_set_event_mgr((opj_common_ptr)dinfo, &event_mgr, stderr);
  opj_setup_decoder(dinfo, &parameters);
  cio = opj_cio_open((opj_common_ptr)dinfo, data, length);
  image = opj_decode(dinfo, cio);
  opj_cio_close(cio);
  opj_destroy_decompress(dinfo);
  
  if (!image) {
    fprintf(stderr, "ERROR -> failed to decode image\n");
    return -1;
  }
  
  if(image->icc_profile_buf) {
    free(image->icc_profile_buf);
    image->icc_profile_buf = NULL;
    image->icc_profile_len = 0;
  }
  
  width = image->comps[0].w;
  height = image->comps[0].h;
  RawBuffer = (unsigned char *) malloc(width * height *3);
  if (RawBuffer == NULL) {
    return -1;
  }

  for (int i = 0; i < width * height; i++) {
    RawBuffer[i*3] = image->comps[0].data[i];
    RawBuffer[i*3+1] = image->comps[1].data[i];
    RawBuffer[i*3+2] = image->comps[2].data[i];
  }

  opj_image_destroy(image);
  free(Jpeg2000Buffer);
  Jpeg2000Buffer = NULL;

  return 0;
}
